<?php
class Table {
	
	/**
	 * MySQLI connection to the database
	 * @var mysqli
	 */
	private static $mysqli;
	private static $fieldQuery;
	
	private $databaseName;
	private $tableName;
	
	public $fields;
	/**
	 * Array containing the {@link Field} in the table ordered
	 * by how they are listed in the database
	 * @var array
	 */
	public $fieldsOrdered;
	
	public $primaryKeys;
	
	/**
	 * Statement containing the select clause to retrieve
	 * every field of this table.
	 * @var mysqli_stmt
	 */
	private $selectStatement;
	
	/**
	 * Insert statement specifying every field in this table
	 * @var mysqli_stmt
	 */
	private $insertStatement;
	
	public function __construct($tableName) {
		if(!isset(self::$mysqli))
			throw new MySQLConnectionException('No database connection has been defined');
		$dbNameResult = self::$mysqli->query('SELECT DATABASE();');
		list($databaseName) = $dbNameResult->fetch_array();
		$dbNameResult->free();
		if($databaseName == null)
			throw new ModelException('You have not selected any database!');
		$this->databaseName = $databaseName;
		$this->tableName = $tableName;
		$this->retrieveFields();
		$this->prepareSelectStatement();
		$this->prepareInsertStatement();
	}
	
	public static function prepareStatements(mysqli $mysqli) {
		self::$mysqli = $mysqli;
		self::prepareFieldRetrievalStatement();
	}
	
	public static function prepareFieldRetrievalStatement() {
		$sql = <<<End
SELECT
	`cols`.`COLUMN_NAME`, `cols`.`ORDINAL_POSITION`, `cols`.`COLUMN_DEFAULT`, `cols`.`IS_NULLABLE` = 'NO',
	`cols`.`DATA_TYPE`, `cols`.`CHARACTER_MAXIMUM_LENGTH`, `cols`.`EXTRA` = 'auto_increment',
	`cols_usage`.`CONSTRAINT_NAME` = 'PRIMARY'
FROM `information_schema`.`COLUMNS` cols
LEFT JOIN `information_schema`.`KEY_COLUMN_USAGE` cols_usage
	ON `cols_usage`.`TABLE_SCHEMA` = `cols`.`TABLE_SCHEMA`
	AND `cols_usage`.`TABLE_NAME` = `cols`.`TABLE_NAME`
	AND `cols_usage`.`COLUMN_NAME` = `cols`.`COLUMN_NAME`
	AND `cols_usage`.`CONSTRAINT_NAME` = 'PRIMARY'
WHERE `cols`.`TABLE_SCHEMA` = ?
AND `cols`.`TABLE_NAME` = ?
ORDER BY `cols`.`ORDINAL_POSITION`
End;
		self::$fieldQuery = self::$mysqli->prepare($sql);
	}
	
	private function retrieveFields() {
		$this->fields = array();
		$this->fieldsOrdered = array();
		$this->primaryKeys = array();
		
		self::$fieldQuery->bind_param('ss', $this->databaseName, $this->tableName);
		self::$fieldQuery->execute();
		self::$fieldQuery->store_result();
		self::$fieldQuery->bind_result($columnName, $position, $default, $notNull, $type, $maxLength, $isAI, $isPrimary);
		while(self::$fieldQuery->fetch()) {
			$field = new Field();
			$field->name = $columnName;
			$field->position = $position;
			$field->default = $default;
			$field->notNull = $notNull?true:false;
			$field->type = $type;
			$field->maxLength = $maxLength;
			$field->isAI = $isAI?true:false;
			$field->isPK = $isPrimary?true:false;
			$this->fields[$columnName] = $field;
			$this->fieldsOrdered[] = $field;
			if($isPrimary)
				$this->primaryKeys[] = $field;
		}
	}
	
	private function prepareSelectStatement() {
		$sql = 'SELECT `'.implode('`, `', $this->fieldsOrdered).'` ';
		$sql .= 'FROM `'.$this->databaseName.'`.`'.$this->tableName.'` ';
		$sql .= 'WHERE `'.implode('` = ? AND `', $this->primaryKeys).'` = ?';
		$this->selectStatement = self::$mysqli->prepare($sql);
		if(self::$mysqli->errno > 0) throw MySQLErrorException::findClass(self::$mysqli);
	}
	
	private function prepareInsertStatement() {
		$sql = 'INSERT INTO `'.$this->databaseName.'`.`'.$this->tableName.'` (';
		$sql .= '`'.implode('`, `', $this->fieldsOrdered).'`) ';
		$sql .= 'VALUES ('.str_repeat('?, ', count($this->fieldsOrdered)-1).'?)';
		$this->insertStatement = self::$mysqli->prepare($sql);
		if(self::$mysqli->errno > 0) throw MySQLErrorException::findClass(self::$mysqli);
	}
	
	public function select(array $primaryKeyValues, $types) {
		$statementValues = array(&$types);
		foreach($primaryKeyValues as $key => $value)
			array_push($statementValues, &$primaryKeyValues[$key]);
		call_user_func_array(array(&$this->selectStatement, 'bind_param'), $statementValues);
		$this->selectStatement->execute();
		if(self::$mysqli->errno > 0) throw MySQLErrorException::findClass(self::$mysqli);
		$this->selectStatement->store_result();
		$values = null;
		if($this->selectStatement->num_rows() != 0) {
			$metadata = $this->selectStatement->result_metadata();
			$values = array();
			while ($field = $metadata->fetch_field())
				$fields[] = &$values[$field->name]; 
			call_user_func_array(array(&$this->selectStatement, 'bind_result'), $fields);
			$this->selectStatement->fetch();
		}
		$this->selectStatement->free_result();
		return $values;
	}
	
	public function insert(array $insertValues, $types) {
		$statementValues = array(&$types);
		foreach($insertValues as $key => $value)
			array_push($statementValues, &$insertValues[$key]);
		call_user_func_array(array(&$this->insertStatement, 'bind_param'), $statementValues);
		$this->insertStatement->execute();
		if(self::$mysqli->errno > 0) throw MySQLErrorException::findClass(self::$mysqli);
		return self::$mysqli->insert_id;
	}
	
	public function update(array $updateValues, array $primaryKeyValues, $types) {
		$sql = 'UPDATE `'.$this->databaseName.'`.`'.$this->tableName.'` ';
		$sql .= 'SET `'.implode('` = ?, `', array_keys($updateValues)).'` = ? ';
		$sql .= 'WHERE `'.implode('` = ? AND `', array_keys($primaryKeyValues)).'` = ?';
		$updateStatement = self::$mysqli->prepare($sql);
		$statementValues = array(&$types);
		foreach($updateValues as $key => $value)
			array_push($statementValues, &$updateValues[$key]);
		foreach($primaryKeyValues as $key => $value)
			array_push($statementValues, &$primaryKeyValues[$key]);
		call_user_func_array(array(&$updateStatement, 'bind_param'), $statementValues);
		$updateStatement->execute();
		if(self::$mysqli->errno > 0) throw MySQLErrorException::findClass(self::$mysqli);
	}
}
?>